<template>
	<div v-if="aiData" :class="$style.container">
		<div :class="{ [$style.tree]: true, [$style.slim]: slim }">
			<ElTree
				:data="executionTree"
				:props="{ label: 'node' }"
				default-expand-all
				:indent="12"
				:expand-on-click-node="false"
				data-test-id="lm-chat-logs-tree"
				@node-click="onItemClick"
			>
				<template #default="{ node, data }">
					<div
						:class="{
							[$style.treeNode]: true,
							[$style.isSelected]: isTreeNodeSelected(data),
						}"
						:data-tree-depth="data.depth"
						:style="{ '--item-depth': data.depth }"
					>
						<button
							v-if="data.children.length"
							:class="$style.treeToggle"
							@click="toggleTreeItem(node)"
						>
							<font-awesome-icon :icon="node.expanded ? 'angle-down' : 'angle-up'" />
						</button>
						<n8n-tooltip :disabled="!slim" placement="right">
							<template #content>
								{{ node.label }}
							</template>
							<span :class="$style.leafLabel">
								<NodeIcon :node-type="getNodeType(data.node)!" :size="17" />
								<span v-if="!slim" v-text="node.label" />
							</span>
						</n8n-tooltip>
					</div>
				</template>
			</ElTree>
		</div>
		<div :class="$style.runData">
			<div v-if="selectedRun.length === 0" :class="$style.empty">
				<n8n-text size="large">
					{{
						$locale.baseText('ndv.output.ai.empty', {
							interpolate: {
								node: props.node.name,
							},
						})
					}}
				</n8n-text>
			</div>
			<div
				v-for="(data, index) in selectedRun"
				:key="`${data.node}__${data.runIndex}__index`"
				data-test-id="lm-chat-logs-entry"
			>
				<RunDataAiContent :input-data="data" :content-index="index" />
			</div>
		</div>
	</div>
</template>

<script lang="ts" setup>
import type { Ref } from 'vue';
import { computed, ref, watch } from 'vue';
import type { ITaskSubRunMetadata, ITaskDataConnections } from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import type { IAiData, IAiDataContent, INodeUi } from '@/Interface';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import NodeIcon from '@/components/NodeIcon.vue';
import RunDataAiContent from './RunDataAiContent.vue';
import { ElTree } from 'element-plus';

interface AIResult {
	node: string;
	runIndex: number;
	data: IAiDataContent | undefined;
}
interface TreeNode {
	node: string;
	id: string;
	children: TreeNode[];
	depth: number;
	startTime: number;
	runIndex: number;
}
export interface Props {
	node: INodeUi;
	runIndex: number;
	hideTitle?: boolean;
	slim?: boolean;
}
const props = withDefaults(defineProps<Props>(), { runIndex: 0 });
const workflowsStore = useWorkflowsStore();
const nodeTypesStore = useNodeTypesStore();
const selectedRun: Ref<IAiData[]> = ref([]);

function isTreeNodeSelected(node: TreeNode) {
	return selectedRun.value.some((run) => run.node === node.node && run.runIndex === node.runIndex);
}

function getReferencedData(
	reference: ITaskSubRunMetadata,
	withInput: boolean,
	withOutput: boolean,
): IAiDataContent[] {
	const resultData = workflowsStore.getWorkflowResultDataByNodeName(reference.node);

	if (!resultData?.[reference.runIndex]) {
		return [];
	}

	const taskData = resultData[reference.runIndex];

	if (!taskData) {
		return [];
	}

	const returnData: IAiDataContent[] = [];

	function addFunction(data: ITaskDataConnections | undefined, inOut: 'input' | 'output') {
		if (!data) {
			return;
		}

		Object.keys(data).map((type) => {
			returnData.push({
				data: data[type][0],
				inOut,
				type: type as NodeConnectionType,
				metadata: {
					executionTime: taskData.executionTime,
					startTime: taskData.startTime,
				},
			});
		});
	}

	if (withInput) {
		addFunction(taskData.inputOverride, 'input');
	}
	if (withOutput) {
		addFunction(taskData.data, 'output');
	}

	return returnData;
}

function toggleTreeItem(node: { expanded: boolean }) {
	node.expanded = !node.expanded;
}

function onItemClick(data: TreeNode) {
	const matchingRun = aiData.value?.find(
		(run) => run.node === data.node && run.runIndex === data.runIndex,
	);
	if (!matchingRun) {
		selectedRun.value = [];

		return;
	}
	selectedRun.value = [
		{
			node: data.node,
			runIndex: data.runIndex,
			data: getReferencedData(
				{
					node: data.node,
					runIndex: data.runIndex,
				},
				true,
				true,
			),
		},
	];
}

function getNodeType(nodeName: string) {
	const node = workflowsStore.getNodeByName(nodeName);
	if (!node) {
		return null;
	}
	const nodeType = nodeTypesStore.getNodeType(node?.type);

	return nodeType;
}

function selectFirst() {
	if (executionTree.value.length && executionTree.value[0].children.length) {
		onItemClick(executionTree.value[0].children[0]);
	}
}

const createNode = (
	nodeName: string,
	currentDepth: number,
	r?: AIResult,
	children: TreeNode[] = [],
): TreeNode => ({
	node: nodeName,
	id: nodeName,
	depth: currentDepth,
	startTime: r?.data?.metadata?.startTime ?? 0,
	runIndex: r?.runIndex ?? 0,
	children,
});

function getTreeNodeData(nodeName: string, currentDepth: number): TreeNode[] {
	const { connectionsByDestinationNode } = workflowsStore.getCurrentWorkflow();
	const connections = connectionsByDestinationNode[nodeName];
	// eslint-disable-next-line @typescript-eslint/no-use-before-define
	const resultData = aiData.value?.filter((data) => data.node === nodeName) ?? [];

	if (!connections) {
		return resultData.map((d) => createNode(nodeName, currentDepth, d));
	}

	const nonMainConnectionsKeys = Object.keys(connections).filter(
		(key) => key !== NodeConnectionType.Main,
	);
	const children = nonMainConnectionsKeys.flatMap((key) =>
		connections[key][0].flatMap((node) => getTreeNodeData(node.node, currentDepth + 1)),
	);

	if (resultData.length) {
		return resultData.map((r) => createNode(nodeName, currentDepth, r, children));
	}

	children.sort((a, b) => a.startTime - b.startTime);

	return [createNode(nodeName, currentDepth, undefined, children)];
}

const aiData = computed<AIResult[] | undefined>(() => {
	const resultData = workflowsStore.getWorkflowResultDataByNodeName(props.node.name);

	if (!resultData || !Array.isArray(resultData)) {
		return;
	}

	const subRun = resultData[props.runIndex].metadata?.subRun;
	if (!Array.isArray(subRun)) {
		return;
	}
	// Extend the subRun with the data and sort by adding execution time + startTime and comparing them
	const subRunWithData = subRun.flatMap((run) =>
		getReferencedData(run, false, true).map((data) => ({ ...run, data })),
	);

	subRunWithData.sort((a, b) => {
		const aTime = a.data?.metadata?.startTime || 0;
		const bTime = b.data?.metadata?.startTime || 0;
		return aTime - bTime;
	});

	return subRunWithData;
});

const executionTree = computed<TreeNode[]>(() => {
	const rootNode = props.node;

	const tree = getTreeNodeData(rootNode.name, 0);
	return tree || [];
});

watch(() => props.runIndex, selectFirst, { immediate: true });
</script>

<style lang="scss" module>
.treeToggle {
	border: none;
	background-color: transparent;
	padding: 0 var(--spacing-3xs);
	margin: 0 calc(-1 * var(--spacing-3xs));
	cursor: pointer;
}
.leafLabel {
	display: flex;
	align-items: center;
	gap: var(--spacing-3xs);
}
.empty {
	padding: var(--spacing-l);
}
.title {
	font-size: var(--font-size-s);
	margin-bottom: var(--spacing-xs);
}
.tree {
	flex-shrink: 0;
	min-width: 12.8rem;
	height: 100%;
	border-right: 1px solid var(--color-foreground-base);
	padding-right: var(--spacing-xs);
	padding-left: var(--spacing-2xs);
	&.slim {
		min-width: auto;
	}
}
.runData {
	width: 100%;
	height: 100%;
	overflow: auto;
}
.container {
	height: 100%;
	padding: 0 var(--spacing-xs);
	display: flex;

	:global(.el-tree > .el-tree-node) {
		position: relative;
		&:after {
			content: '';
			position: absolute;
			top: 2rem;
			bottom: 1.2rem;
			left: 0.75rem;
			width: 0.125rem;
			background-color: var(--color-foreground-base);
		}
	}
	:global(.el-tree-node__expand-icon) {
		display: none;
	}
	:global(.el-tree) {
		margin-left: calc(-1 * var(--spacing-xs));
	}
	:global(.el-tree-node__content) {
		margin-left: var(--spacing-xs);
	}
}
.isSelected {
	background-color: var(--color-foreground-base);
}
.treeNode {
	display: inline-flex;
	border-radius: var(--border-radius-base);
	align-items: center;
	gap: var(--spacing-3xs);
	padding: var(--spacing-4xs) var(--spacing-3xs);
	font-size: var(--font-size-xs);
	color: var(--color-text-dark);
	margin-bottom: var(--spacing-3xs);
	cursor: pointer;

	&:hover {
		background-color: var(--color-foreground-base);
	}
	&[data-tree-depth='0'] {
		margin-left: calc(-1 * var(--spacing-2xs));
	}

	&:after {
		content: '';
		position: absolute;
		margin: auto;
		background-color: var(--color-foreground-base);
		height: 0.125rem;
		left: 0.75rem;
		width: calc(var(--item-depth) * 0.625rem);
	}
}
</style>
